package br.mikhas.lab.lang;

import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;

import br.mikhas.lab.lang.tables.ClassTable;
import br.mikhas.lab.lang.tables.FieldTable;
import br.mikhas.lab.lang.tables.MethodTable;
import br.mikhas.lab.lang.tables.StringTable;
import br.mikhas.util.ExceptionUtils;
import br.mikhas.util.IOUtils;
import br.mikhas.util.StringUtils;

public class SimpleCompiler {

	public synchronized void compile(File source, File compiled) {
		BufferedReader reader = null;
		OutputStream output = null;
		try {
			reader = new BufferedReader(new FileReader(source));
			output = new BufferedOutputStream(new FileOutputStream(compiled));

			createCompiled(reader, output);
		} catch (Exception e) {
			ExceptionUtils.throwWrapped(e);
		} finally {
			IOUtils.close(reader);
			IOUtils.close(output);
		}
	}

	private static final String CLASS = "CLASS";
	private static final String END_CLASS = "END CLASS";
	private static final String FIELD = "FIELD";
	private static final String METHOD = "METHOD";
	private static final String END_METHOD = "END METHOD";
	private FieldTable fields;
	private ClassTable classes;
	private StringTable strings;
	private MethodTable methods;

	private void createCompiled(BufferedReader reader, OutputStream output)
			throws IOException {

		strings = new StringTable();
		classes = new ClassTable();
		fields = new FieldTable();
		methods = new MethodTable();

		String line = null;
		while ((line = reader.readLine()) != null) {
			if (line.startsWith("#"))
				continue;

			if (line.startsWith(CLASS)) {
				processClass(line, reader);
			}
		}

		System.out.println("READING DONE");

		displayData();
	}

	private void displayData() {
		int count = 0;
		for (String string : strings.getAll()) {
			System.out.println(count + " " + string);
			count++;
		}

		for (Class c : classes.getAll())
			System.out.println(c);

		for (Field f : fields.getAll())
			System.out.println(f);

		for (Method m : methods.getAll())
			System.out.println(m);
	}

	private void processClass(String line, BufferedReader reader)
			throws IOException {
		String[] classLine = StringUtils.split(line, ' ');
		String className = classLine[1];
		Class cls = getClass(className);

		String parentClassName;
		int parent = -1;

		if (classLine.length > 2) {
			parentClassName = classLine[3];
			parent = classes.get(new Class(classes, parentClassName, strings));
			cls.setParent(parent);
		}

		while ((line = reader.readLine()) != null) {
			if (line.startsWith("#"))
				continue;

			if (line.startsWith(FIELD))
				processField(line, cls, reader);

			if (line.startsWith(METHOD))
				processMethod(line, cls, reader);

			if (line.startsWith(END_CLASS))
				return;
		}

	}

	private void processMethod(String line, Class cls, BufferedReader reader)
			throws IOException {
		String[] methodLine = StringUtils.split(line, ' ');
		String returnType = methodLine[1];
		String methodName = methodLine[2];

		int paramStart = methodName.indexOf('(');
		if (paramStart >= 0)
			methodName = methodName.substring(0, paramStart);

		MethodBuilder builder = new MethodBuilder();
		builder.name(methodName).returnType(getClass(returnType)).cls(cls);

		paramStart = line.indexOf('(');
		int paramEnd = line.indexOf(')', paramStart);

		if (paramEnd - paramStart > 1) {
			String[] params = StringUtils.split(line.substring(paramStart + 1,
					paramEnd), ',');

			for (String p : params) {
				builder.parameter(getClass(p.trim()));
			}
		}

		Method method = builder.build();

		while ((line = reader.readLine()) != null) {
			method.addCodeLine(StringUtils.trim(line));
			if (line.startsWith(END_METHOD))
				return;
		}
	}

	private void processField(String line, Class cls, BufferedReader reader) {
		String[] fieldLine = StringUtils.split(line, ' ');
		String fieldType = fieldLine[1];
		String fieldName = fieldLine[2];

		Class type = getClass(fieldType);

		new Field(strings, fields, fieldName, type, cls);

	}

	private Class getClass(String name) {
		int typeId = classes.get(new Class(classes, name, strings));
		return classes.get(typeId);
	}

	private class MethodBuilder {
		String name;
		Class cls;
		Class returnType;
		List<Class> parameters = new ArrayList<Class>();

		public MethodBuilder() {

		}

		public Method build() {
			String name = cls.getName() + '.' + this.name + '(';

			boolean first = true;
			for (Class c : parameters) {
				if (!first)
					name = name + ',';
				first = false;
				name += c.getName();
			}
			name += ")";
			return new Method(strings, classes, methods, cls, name, returnType);
		}

		MethodBuilder name(String name) {
			this.name = name;
			return this;
		}

		MethodBuilder cls(Class cls) {
			this.cls = cls;
			return this;
		}

		MethodBuilder returnType(Class returnType) {
			this.returnType = returnType;
			return this;
		}

		MethodBuilder parameter(Class cls) {
			parameters.add(cls);
			return this;
		}
	}

	public static void main(String[] args) {

		SimpleCompiler simpleCompiler = new SimpleCompiler();

		simpleCompiler.compile(new File(
				"C:\\Dev\\Workspaces\\general\\mikhas-lab\\lang\\source.mik"),
				new File("H:\\temp\\compilado.cmp"));
		System.out.println("DONE");
	}
}
